package rps.client.ai;

import java.rmi.RemoteException;
import java.util.Vector;

import rps.client.ArrayShuffle;
import rps.client.GameListener;
import rps.game.Game;
import rps.game.data.Figure;
import rps.game.data.Player;
import rps.game.data.FigureKind;

/**
 * This class contains a very basic AI, that allows to play a game against it.
 * The main benefit is to be able to test the UI.
 */
public class BasicAi implements GameListener {

	private Player player = new Player("Basic AI");
	private Game game;
	private FigureKind[] preAssignment = new FigureKind[42];
	private int rows = 6, cols = 7;
	private int moveFrom, moveTo;

	/**
	 * returns a random integer
	 * 
	 * @param low
	 * @param high
	 * @return
	 */
	private int myRandom(int low, int high) {
		high++;
		return (int) (Math.random() * (high - low) + low);
	}

	/**
	 * makes a random choice
	 * 
	 * @return returns a FigureKind (ROCK, PAPER or SCISSORS)
	 */
	private FigureKind makeChoice() {
		int x = myRandom(1, 3);
		switch (x) {
		case 1: {
			return FigureKind.PAPER;
		}
		case 2: {
			return FigureKind.ROCK;
		}
		case 3: {
			return FigureKind.SCISSORS;
		}
		default:
			return FigureKind.ROCK;
		}
	}

	/**
	 * generates a random Assignment
	 * 
	 * @param defaultAssignment
	 */
	private void generateRandomAssignment(FigureKind[] defaultAssignment) {

		ArrayShuffle.shuffle(defaultAssignment);

		for (int i = 0; i < (42 - 14); i++) {
			preAssignment[i] = null;
		}
		for (int i = 0; i < 14; i++) {
			preAssignment[28 + i] = defaultAssignment[i];
		}
	}

	/**
	 * checks if the given Figure can be moved
	 * 
	 * @param f
	 *            Figure
	 * @return true if f belongs to player and is not a FLAG or a TRAP
	 */
	private boolean validateFigure(Figure f) {
		boolean result = ((f.getKind() != FigureKind.FLAG) && (f.getKind() != FigureKind.TRAP));
		return ((f.belongsTo(player)) && result);
	}

	/**
	 * checks if row and col are inside the boards borders
	 * 
	 * @param row
	 * @param col
	 * @return true if row and col are inside the field, else false
	 */
	private boolean validateField(int row, int col) {
		return ((row >= 0) && (row < rows) && (col >= 0) && (col < cols));
	}

	/**
	 * checks if the the given row and col points to a valid destination
	 * 
	 * @param field
	 *            board
	 * @param row
	 * @param col
	 * @return true if ai can move to this field, else false
	 */
	private boolean checkField(Figure[] field, int row, int col) {
		return ((field[toIndex(row, col)] == null) || !(field[toIndex(row, col)]
				.belongsTo(player)));
	}

	/**
	 * creates an array of integer
	 * 
	 * @param start
	 *            starting position
	 * @param end
	 *            destination
	 * @return returns an array of int
	 */
	private int[] makeMove(int start, int end) {
		int[] move = new int[2];
		move[0] = start;
		move[1] = end;
		return move;
	}

	private int toIndex(int row, int col) {
		return (col + row * cols);
	}

	private boolean validateDestination(Figure[] field, int row, int col) {
		if (validateField(row, col) && (checkField(field, row, col)))
			return true;
		return false;
	}

	/**
	 * checks the adjacent fields around the current field and returns those to
	 * which the ai can move
	 * 
	 * @param field
	 *            board
	 * @param row
	 * @param col
	 * @return returns a Vector of integer arrays
	 */
	private Vector<int[]> possibleMoves(Figure[] field, int row, int col) {
		Vector<int[]> moves = new Vector<int[]>();
		// check field to the left of the current field
		int x = col, y = row, fig = toIndex(y, x);
		if (validateDestination(field, row, --col))
			moves.add(makeMove(fig, toIndex(row, col)));
		// check field to the right of the current field
		col = x;
		if (validateDestination(field, row, ++col))
			moves.add(makeMove(fig, toIndex(row, col)));
		// check field above the current field
		col = x;
		if (validateDestination(field, --row, col))
			moves.add(makeMove(fig, toIndex(row, col)));
		// check field under the current field
		row = y;
		if (validateDestination(field, ++row, col))
			moves.add(makeMove(fig, toIndex(row, col)));
		return moves;
	}

	/**
	 * checks all possible moves and stores a random one in moveFrom and moveTo
	 * returns true if a move was found, else false
	 */
	private boolean nextMove() {
		try {
			int index;
			Vector<int[]> moves = new Vector<int[]>();
			Figure[] field = game.getField();
			for (int j = 0; j < rows; ++j)
				for (int i = 0; i < cols; ++i)
					if (field[toIndex(j, i)] != null)
						if (validateFigure(field[toIndex(j, i)])) {
							Vector<int[]> newmoves = possibleMoves(field, j, i);
							moves.addAll(newmoves);
						}
			if (moves.size() > 0) {
				index = myRandom(0, moves.size() - 1);
				int[] move = new int[2];
				move = moves.get(index);
				moveFrom = move[0];
				moveTo = move[1];
				return true;
			} else
				return false;
		} catch (RemoteException e) {
			e.printStackTrace();
			return false;
		}

	}

	@Override
	public void chatMessage(Player sender, String message)
			throws RemoteException {
		if (!player.equals(sender)) {
			game.sendMessage(player, "you said: " + message);
		}
	}

	@Override
	public void provideInitialAssignment(Game game) throws RemoteException {
		this.game = game;
		FigureKind[] preAssignment = new FigureKind[] { FigureKind.ROCK,
				FigureKind.ROCK, FigureKind.ROCK, FigureKind.ROCK,
				FigureKind.PAPER, FigureKind.PAPER, FigureKind.PAPER,
				FigureKind.PAPER, FigureKind.SCISSORS, FigureKind.SCISSORS,
				FigureKind.SCISSORS, FigureKind.SCISSORS, FigureKind.TRAP,
				FigureKind.FLAG };
		generateRandomAssignment(preAssignment);
		try {
			game.setInitialAssignment(player, this.preAssignment);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void provideInitialChoice() throws RemoteException {
		FigureKind choice = makeChoice();
		game.setInitialChoice(player, choice);
	}

	@Override
	public void startGame() throws RemoteException {
		game.sendMessage(player, "Lass uns kämpfen!");
	}

	@Override
	public void provideNextMove() throws RemoteException {
			if (nextMove())
				game.move(player, moveFrom, moveTo);
	}

	@Override
	public void figureMoved() throws RemoteException {
		// do nothing
	}

	@Override
	public void figureAttacked() throws RemoteException {
		// do nothing
	}

	@Override
	public void provideChoiceAfterFightIsDrawn() throws RemoteException {
		game.setUpdatedKindAfterDraw(player, makeChoice());
	}

	@Override
	public void gameIsLost() throws RemoteException {
		game.sendMessage(player, "Das nächste mal gewinne ich!");
	}

	@Override
	public void gameIsWon() throws RemoteException {
		game.sendMessage(player, "Du hast noch viel zu lernen!");
	}

	@Override
	public void gameIsDrawn() throws RemoteException {
		game.sendMessage(player, "So ein Mist...");
	}

	@Override
	public String toString() {
		return player.getNick();
	}

	@Override
	public Player getPlayer() throws RemoteException {
		return player;
	}
}